package org.koin.core.parameter;

import org.koin.core.error.DefinitionParameterException;
import org.koin.core.error.NoParameterFoundException;
import org.koin.ext.ClassUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class DefinitionParameters {

    public static final int MAX_PARAMS = 5;
    private List<Object> _values;

    public DefinitionParameters() {
        this._values = new ArrayList<>();
    }

    public DefinitionParameters(List<Object> values) {
        this._values = new ArrayList<>();
        this._values.addAll(values);
    }

    public static DefinitionParameters parametersOf(Object... parameters)
            throws DefinitionParameterException {
        if (parameters == null) {
            List<Object> list = new ArrayList<>();
            list.add(null);
            return new DefinitionParameters(list);
        } else if (parameters.length <= MAX_PARAMS) {
            return new DefinitionParameters(Arrays.asList(parameters));
        } else {
            throw new DefinitionParameterException(
                    "Can't build DefinitionParameters for more than " + MAX_PARAMS + " arguments"
            );
        }
    }

    public <T> T elementAt(int i, Class clazz) throws NoParameterFoundException {
        if (_values.size() > i) {
            return (T) _values.get(i);
        } else {
            throw new NoParameterFoundException(
                    "Can't get injected parameter #" + i
                            + " from $this for type '"
                            + ClassUtil.getFullName(clazz) + "'"
            );
        }
    }

    public <T> T component1(Class<T> tClass) throws NoParameterFoundException {
        return elementAt(0, tClass);
    }

    public <T> T component2(Class<T> tClass) throws NoParameterFoundException {
        return elementAt(1, tClass);
    }

    public <T> T component3(Class<T> tClass) throws NoParameterFoundException {
        return elementAt(2, tClass);
    }

    public <T> T component4(Class<T> tClass) throws NoParameterFoundException {
        return elementAt(3, tClass);
    }

    public <T> T component5(Class<T> tClass) throws NoParameterFoundException {
        return elementAt(4, tClass);
    }

    public <T> T get(int i) {
        return (T) _values.get(i);
    }

    public <T> void set(int i, T t) {
        _values.set(i, t);
    }

    public int size() {
        return _values.size();
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public boolean isNotEmpty() {
        return !isEmpty();
    }

    public DefinitionParameters insert(int index, Object value) {
        _values.add(index, value);
        return this;
    }

    public DefinitionParameters add(Object value) {
        return insert(size(), value);
    }

    public <T> T get(Class<T> clazz) throws DefinitionParameterException {
        Object result = getOrNull(clazz);
        if (result == null) {
            throw new DefinitionParameterException(
                    "No value found for type '" + ClassUtil.getFullName(clazz) + "'"
            );
        } else {
            return (T) result;
        }

    }

    public <T> T getOrNull(Class clazz) throws DefinitionParameterException {
        List<T> values = new ArrayList<>();
        for (Object item : _values) {
            if (item.getClass().equals(clazz)) {
                values.add((T) item);
            }
        }
        if (values.size() == 1) {
            return values.get(0);
        }
        if (values.size() == 0) {
            return null;
        }
        throw new DefinitionParameterException(
                "Ambiguous parameter injection: more than one value of type '"
                        + ClassUtil.getFullName(clazz)
                        + "' to get from " + this
                        + ". Check your injection parameters"
        );
    }

    @Override
    public String toString() {
        return "DefinitionParameters{"
                + "_values=" + _values
                + '}';
    }
}
